iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
AI Engineering

30天拆Agent:從Repo看設計系列 第 7 篇

Day 7|HolmesGPT 的 Tool 怎麼設計?

  • 分享至 

  • xImage
  •  

上一篇已經看過 HolmesGPT 自己掌握 Model → Tool → Result → Model 的執行迴圈。

這篇不再重複 Agent Loop,而是往裡面再拆一層:

HolmesGPT 到底怎麼設計 Tool?

HolmesGPT 並不是把所有 Function 放進一個 List,再全部交給模型,而是在中間做了幾層處理:

Toolset
   ↓
ToolsetManager
   ↓
ToolExecutor
   ↓
Tool Schema
   ↓
LLM
   ↓
Tool.invoke()
   ↓
真正執行
   ↓
StructuredToolResult

HolmesGPT 的 Tool 不只是 Function,而是一套從能力註冊、可用性檢查、執行控制,到 Result 回傳都由 Runtime 管理的 Capability System。

這篇主要看其中五個設計。


1. Tool 不是單獨存在,而是先被組成 Toolset

最簡單的 Agent Tool 寫法,可能是:

tools = [
    get_pods,
    query_prometheus,
    run_bash,
    search_web,
]

全部 Function 放進同一個 List,再交給模型。

HolmesGPT 沒有直接這樣做。

它先定義 Toolset(holmes/core/tools.py)。

概念上可以理解成:

Kubernetes Toolset
├── get pods
├── get logs
├── get events
└── ...

Prometheus Toolset
├── query
└── query range

Bash Toolset
└── execute command

如果只看到這裡,Toolset 很像只是分類。

但真正看 Toolset 的欄位,會發現它除了 tools,還管理:

enabled
status
config
prerequisites
tags
approval_required_tools
...

例如 Prometheus Toolset 並不只有「查 Prometheus」這支 Function,它還牽涉:

Prometheus endpoint
Authentication
Config
Network
Runtime 是否能連線

所以 Repo 裡「有 Prometheus Tool」和「這台 HolmesGPT 現在真的能使用 Prometheus」是兩回事。

這也是 Toolset 這層 abstraction 的價值:

把 Tool 和它需要的執行環境一起管理。

同樣的概念也適用在 Kubernetes、Grafana、Database 或其他外部系統。


2. ToolsetManager 決定 Model 這次真正擁有哪些能力

有了 Toolset 之後,下一個問題是:

Repo 裡有 Kubernetes、Prometheus、Grafana 這些 Toolset,是不是代表模型全部都看得到?

不是。

HolmesGPT 會先經過 ToolsetManager(holmes/core/toolset_manager.py)。

它負責載入 Built-in / Custom Toolset,再根據:

enabled
config
tag
prerequisite

判斷目前哪些 Toolset 可以使用。

最核心的流程在:

ToolsetManager._list_all_toolsets()

例如 CLI 模式下,程式會嘗試 Auto-enable Toolset,但如果必要 Config 根本沒有提供,就不會啟用:

if enable_all_toolsets:
    for toolset in toolsets_by_name.values():
        if not toolset.missing_config:
            toolset.enabled = True

接著還會檢查 Toolset 的 Prerequisite。

所以這裡第一個重要區別是:

Repo 裡有這個 Tool
        ≠
目前 Runtime 可以使用這個 Tool

例如 Repo 雖然有 Prometheus Toolset,但如果必要的 Config 或執行條件不存在,就不應該讓後面的 Agent 把它當成正常能力。

接下來怎麼真的影響 Model?

這裡才是最重要的一段。

ToolsetManager 整理完 Toolset 後,會交給 ToolExecutor(holmes/core/tools_utils/tool_executor.py)。

ToolExecutor 初始化時,第一件事就是只留下:

self.enabled_toolsets = [
    ts for ts in toolsets
    if ts.status == ToolsetStatusEnum.ENABLED
]

也就是:

ToolsetManager
      ↓
所有 Toolset 的 Status
      ↓
ToolExecutor
      ↓
只留下 ENABLED Toolset

接著再把這些 Tool 收進:

self.tools_by_name

形成真正可用的 Tool Registry。

概念上:

Kubernetes    ENABLED
Prometheus    ENABLED
Grafana       FAILED
Datadog       DISABLED
        ↓
ToolExecutor
        ↓
get_pods
get_logs
query_prometheus

Grafana 和 Datadog 不會只是「Prompt 告訴模型不要用」。

它們根本不會進入這批可用 Tool。


最後才轉成 Model 看得到的 Tool Schema

ToolExecutor 接著透過:

get_all_tools_openai_format()

把 Registry 裡的 Tool 轉成 Model 可以使用的 Function Schema:

return [
    tool.get_openai_format()
    for tool in self.tools_by_name.values()
]

最後 ToolCallingLLM 取得 Tool 的地方也很直接(holmes/core/tool_calling_llm.py):

def _get_tools(self):
    return self.tool_executor.get_all_tools_openai_format(...)

所以完整 Code Path 其實是:

ToolsetManager
│
│ 判斷 Config / Tag / Prerequisite / Status
▼
Toolset
│
│ 只有 status = ENABLED
▼
ToolExecutor
│
│ 建立 tools_by_name
▼
get_all_tools_openai_format()
│
▼
ToolCallingLLM
│
▼
LLM

這裡就可以很清楚看到 HolmesGPT 的一個設計特色:

LLM 並不是看到所有 Repo 裡存在的 Tool,再靠自己決定哪些不能用;Runtime 會先縮小 Capability Surface,最後只把可用的 Tool Schema 交給 Model。

所以與其說 ToolsetManager 是「Tool Loader」,我覺得更準確的理解是:

它參與決定這個 Agent 目前真正擁有哪些能力。

Model 還是可以在這些能力裡自由決定下一步要用哪個 Tool,但「有哪些能力可以選」,已經先由 Code 限制好了。


3. Tool 有正式 Schema,但真正的關鍵是 Tool.invoke()

Tool 準備提供給 LLM 時,HolmesGPT 會透過 get_openai_format() 把 Tool 轉成模型可以理解的 Function Schema(holmes/core/tools.py)。

模型看到的不是 Python Function 本身,而比較像:

{
  "type": "function",
  "function": {
    "name": "get_pods",
    "description": "Get Kubernetes pods",
    "parameters": {
      "type": "object",
      "properties": {
        "namespace": {
          "type": "string"
        }
      }
    }
  }
}

也就是:

Tool Name
+
Description
+
Parameter Schema

HolmesGPT 的 ToolParameter 也可以描述:

required
object
array
enum
minimum
maximum
pattern
anyOf
...

所以 Model 和 Runtime 之間不是靠自由文字約定,而是有一份正式的 Input Contract。

不過我覺得這還不是 HolmesGPT Tool 最有意思的地方。

真正值得看的,是模型產生 Tool Call 之後發生什麼。


Model 選了 Tool,不代表 Function 直接被執行

HolmesGPT 所有 Tool 都繼承 Tool。

真正核心的是:

Tool.invoke()

(holmes/core/tools.py)

簡化後,它的流程大概是:

Tool Call
   ↓
Tool.invoke()
   ↓
Approval Check
   ↓
Parameter Processing
   ↓
_invoke()
   ↓
Result Processing

真正實作 Tool 行為的是:

_invoke()

例如:

打 API
執行 Command
查 Kubernetes
查 Prometheus
呼叫外部服務

但 Runtime 不會直接跳進 _invoke()。

外面一定先經過:

invoke()

所以可以很簡單地分:

invoke()
= Runtime Contract

_invoke()
= Tool 真正的 Business Logic

這個設計很值得注意。

因為 Tool 作者只需要處理:

這支 Tool 到底怎麼完成工作?

而 Runtime 共通問題,例如:

這次需要 Approval 嗎?
Arguments 要不要處理?
Result 要不要再加工?

可以統一留在 Tool.invoke()。


Approval 就放在真正執行之前

Tool.invoke() 一開始就會檢查這次 Tool Call 是否需要 Approval。

如果需要確認,而且使用者還沒有 Approve,它不會執行 _invoke()。

而是直接回:

APPROVAL_REQUIRED

因此:

LLM
 ↓
Tool Call
 ↓
Tool.invoke()
 ↓
Requires Approval?
 ↓
YES
 ↓
APPROVAL_REQUIRED

真正的 Side Effect 還沒發生。

這個位置非常合理。

因為 Gate 就放在:

Model Decision

和:

Real Execution

中間。

所以 HolmesGPT 可以允許模型有很大的自主性:

自己選 Tool
自己填 Parameters
自己規劃下一步

但這不代表模型也自動取得:

最終執行權

Approval 可以直接設定在 Toolset

Toolset 本身有:

approval_required_tools

例如概念上可以設定:

approval_required_tools:
  - run_kubectl_command

那同一個 Toolset 裡:

查詢資訊的 Tool
→ 可以直接執行

會修改系統的 Tool
→ Human Approval

這比:

整個 Kubernetes Toolset 全部允許

或:

整個 Kubernetes Toolset 全部禁止

更實用。

因為 production Agent 很常遇到這種需求:

Read
→ 可以自動

Write
→ 需要確認

Capability 不一定要整組開、整組關。


Approval 還可以依照這次 Arguments 決定

HolmesGPT 的 Tool 還可以 override:

requires_approval(params, context)

也就是:

同一支 Tool,要不要 Approval,不一定是固定的。

Runtime 可以看這一次的參數。

這對 Bash 特別重要。

因為:

Bash Tool

本身並不能直接被分類成:

安全

或:

危險

真正的風險取決於:

這一次到底執行什麼 Command

所以 HolmesGPT 的設計其實可以做到:

Tool
+
Arguments
→
Execution Policy

而不只是:

Tool Name
→
Allow / Deny

這已經比單純的 Tool Allowlist 細很多。


4. HolmesGPT 對 Tool 不只管 Input,也管 Output

前面看到:

Tool Schema
Approval
Parameter Processing

這些都在控制 Tool Input。

但 HolmesGPT 連 Output 也沒有直接丟回模型。

它定義了 StructuredToolResult(holmes/core/tools.py)。

其中有明確 Status,例如:

SUCCESS
ERROR
NO_DATA
APPROVAL_REQUIRED
FRONTEND_PAUSE

也可以帶:

data
error
return_code
images
url
params
elapsed_seconds

為什麼這件事重要?

因為下面三種狀況其實完全不同:

Tool 成功執行,但是沒有資料
Tool 執行失敗
Tool 根本還沒執行,正在等 Approval

如果 Tool 一律只回:

"No result"

下一輪 Model 還需要猜:

沒資料?
API 壞了?
Permission Error?
還是操作根本沒被執行?

Structured Result 讓 Runtime 可以把:

Execution State

和:

Actual Data

一起帶回 Agent。

這讓 Tool Result 不只是 Observation Content,也帶著 Observation Status。


Tool Output 還可以經過 Transformer

SRE Agent 還有一個很現實的問題:

Tool Output 很容易超大。

例如:

kubectl logs
大量 Kubernetes JSON
Prometheus Result
數萬行 Log

如果每次都:

Tool
 ↓
50,000 lines
 ↓
直接塞回 LLM

Context 很快就會膨脹。

所以 HolmesGPT 在 _invoke() 執行之後,還會經過 _apply_transformers()(holmes/core/tools.py)。

概念上:

Raw Result
   ↓
Transformer
   ↓
Processed Result
   ↓
LLM

例如進行:

Filtering
Summarization
Truncation
其他 Result Processing

這個設計其實很有 Agent 味。

一般寫 API Client,只要處理:

API 有沒有成功?

Agent Runtime 還必須考慮:

這份結果適不適合再次送進模型?

例如一萬行 Log 對人和程式來說都是合法結果,但對下一輪 LLM 未必是一個好的 Observation。

所以 HolmesGPT 的 Tool abstraction 不只包含:

怎麼取得資料

也包含:

資料取得之後,
怎麼變成下一輪 Model 可以使用的 Context。

這也是為什麼我會覺得它比單純的 Function Calling 多了一層 Runtime 設計。


不同來源的 Tool 最後盡量走同一條 Pipeline

HolmesGPT 的 Tool 可以來自不同地方。

例如:

Built-in Tool
Custom YAML Tool
Python Tool
MCP Tool

但它們最後盡量被收斂成:

Tool / Toolset
      ↓
Tool Runtime
      ↓
Structured Result

例如 YAMLTool 本身仍然繼承 Tool(holmes/core/tools.py)。

所以 Custom YAML 不是:

YAML
 ↓
直接執行 Shell

而是:

YAML
 ↓
YAMLTool
 ↓
Toolset
 ↓
Tool.invoke()
 ↓
Result

MCP 也是類似概念。

MCP 解決的是:

外部能力怎麼被接進來?

但 HolmesGPT 還是可以在自己的 Runtime 決定:

這個 Tool 要不要暴露
需不需要 Approval
執行結果怎麼回到 Model

也就是:

Built-in
YAML
Python
MCP
   ↓
Tool / Toolset
   ↓
同一套 Execution Contract

這讓 Runtime 不會因為 Integration 來源不同,就出現完全不同的 Tool 行為。


5. 用 Bash Tool 看這套設計是不是真的有用

前面都比較像架構。

Bash Tool 是一個很好理解的實際案例,因為它最容易出現 Side Effect。

相關程式在:

holmes/plugins/toolsets/bash/bash_toolset.py

如果模型產生:

bash(command="...")

HolmesGPT 並不是收到之後直接執行。

Bash Tool 會先做 Command Validation。

概念上可以分成:

LLM 產生 Command
        ↓
Command Validation
        ↓
 ┌──────┼──────────────┐
 │      │              │
Allow  Deny     Approval Required
 │      │              │
執行    拒絕          等使用者確認

如果 Command 需要人工確認,requires_approval() 會回:

ApprovalRequirement

然後回到共用的:

Tool.invoke()

invoke() 發現:

needs_approval = true

就直接停下。

所以真正的 Command 還沒有被執行。


這不是「Bash Tool 危險,所以全部擋掉」

這裡我覺得設計最實用的地方是:

HolmesGPT 並沒有簡單把:

Bash

分類成:

Dangerous Tool

然後每次都要求確認。

因為不同 Command 的風險差很多。

例如:

pwd

和一個會修改 production resource 的 Command,明顯不應該使用同樣的 Policy。

所以 Bash Tool 可以依照 Command 本身做 Validation。

這就回到前面提到的:

Tool
+
Arguments
→
真正的 Risk

而不是只有:

Tool Name
→
Risk

對企業 Agent 來說,這個差異很重要。

因為很多 Tool 都同時包含 Read 與 Write 能力。

例如:

Database Tool
Cloud Tool
Kubernetes Tool
Internal Admin API

如果只能做到:

整支 Tool 開

或:

整支 Tool 關

最後往往不是權限太大,就是 Agent 幾乎什麼都不能做。

HolmesGPT 的設計至少提供了一個方向:

把 Policy 往實際 Action 與 Arguments 靠近。


_invoke() 裡還會再檢查一次

Bash Tool 還有一個我覺得非常值得看的細節。

理論上,前面的:

requires_approval()

已經應該把需要 Approval 的 Command 擋住。

但真正進入 _invoke() 時,Command 還會再 Validation 一次。

也就是:

第一層

Tool.invoke()
↓
Approval Check

後面還有:

第二層

Bash _invoke()
↓
Command Validation

如果一個理論上需要 Approval 的 Command,竟然在沒有 Approval 的狀態下跑進 _invoke(),程式不會假設:

既然已經到這裡,應該沒問題。

而是回 Error。

這就是很典型的:

Defense in Depth

系統不是假設前面那層永遠不可能出 Bug,而是在真正 Side Effect 前,再確認一次。

這一點比:

System Prompt:
危險操作前一定要先問使用者。

可靠很多。

因為 Prompt 解決的是:

Model 應該怎麼行為

這裡解決的是:

就算 Model 或前面的流程出錯,
Real Execution 還能不能發生。

結論

所以我現在會把 HolmesGPT 的 Tool 設計理解成:

它不是替 LLM 掛上一堆 Function,而是替 Agent 建立一個 Capability Runtime。

Model 可以負責:

我要用哪個 Tool?
Arguments 要填什麼?
下一步要查什麼?

Runtime 則負責:

這個能力現在存在嗎?
這次能不能使用?
這個 Action 要不要 Approval?
Input 是否符合 Contract?
真正能不能執行?
Result 要怎麼回到下一輪?

這個切分,比「支援多少 Tool」更值得拿來比較不同 Agent Framework。

因為模型能力會一直變強,Tool 數量也可以一直增加。

但只要 Agent 要開始碰:

Production System
Internal API
Database
Cloud Resource
Command Execution

最終都會遇到同一個問題:

Model 想做一件事,和系統真的允許它做這件事,中間到底隔著什麼?

HolmesGPT 的答案,就是這套 Tool Runtime。

References


上一篇
Day 6|HolmesGPT:當 Agent 自己掌握 Tool Loop,控制點會放在哪裡?
下一篇
Day 8|HolmesGPT 的 Memory 有什麼特別?從 Checkpoint、Resume 到 Feedback
系列文
30天拆Agent:從Repo看設計 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言